from pysqlite2 import dbapi2 as sqlite

SQL_getDirId = "SELECT id FROM dirs WHERE parent=? AND name=?;"
SQL_addDir = "INSERT INTO dirs (parent, name) VALUES (?, ?);";
SQL_getFile = "SELECT id, contents, mtime FROM files WHERE dir=? AND name=?;";
SQL_addFile = "INSERT INTO files (contents, dir, name, mtime) VALUES (?, ?, ?, ?);";
SQL_addContents = "INSERT INTO contents (size, md5, refs) VALUES (?, ?, 0);";
SQL_getContentsId = "SELECT id FROM contents WHERE md5=? AND size=?;";
SQL_getContentsRefs = "SELECT refs FROM contents WHERE id=?;";
SQL_incContentsRefs = "UPDATE contents SET refs=refs+1 WHERE id=?;";
SQL_decContentsRefs = "UPDATE contents SET refs=refs-1 WHERE id=?;"
SQL_delContents = "DELETE FROM contents WHERE id=?;"

/** FileDB constructor
 */
FileDB::FileDB(const char* file) {
  if (sqlite3_open(file, &db) != SQLITE_OK) {
    throw sqlite3_errmsg(db);
  }
}

FileDB::~FileDB() {
  sqlite3_close(db);
}

int FileDB::get_dir_id(const char* name, int pathId) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_getDirId, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, pathId);
  sqlite3_bind_text(stmt, 2, name, -1, SQLITE_TRANSIENT);

  int ret = sqlite3_step(stmt);
  if (ret == SQLITE_DONE) return 0;
  if (ret != SQLITE_ROW) {
    perror(sqlite3_errmsg(db));
    exit(1);
  }

  int id = sqlite3_column_int(stmt, 0);

  if (sqlite3_step(stmt) != SQLITE_DONE) {
    perror("Directory multiple times!");
    exit(1);
  }

  sqlite3_finalize(stmt);
  return id;
}

void FileDB::add_dir(const char* name, int parentId) {
  cout << "add_dir" << endl;
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_addDir, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, parentId);
  sqlite3_bind_text(stmt, 2, name, -1, SQLITE_TRANSIENT);

  int ret = sqlite3_step(stmt);
  if (ret != SQLITE_DONE) {
    cerr << "Error adding directory (" << name << ", " << parentId << "): " << sqlite3_errmsg(db) << endl;
  }
  sqlite3_finalize(stmt);
}

int FileDB::get_file_id(int pathId, const char* name, int* contentsId, time_t* mTime) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_getFile, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, pathId);
  sqlite3_bind_text(stmt, 2, name, -1, SQLITE_TRANSIENT);

  int ret = sqlite3_step(stmt);
  if (ret == SQLITE_DONE) return 0;
  if (ret != SQLITE_ROW) {
    perror(sqlite3_errmsg(db));
    exit(1);
  }

  int id = sqlite3_column_int(stmt, 0);
  *contentsId = sqlite3_column_int(stmt, 1);
  *mTime = sqlite3_column_int(stmt, 2);

  if (sqlite3_step(stmt) != SQLITE_DONE) {
    perror("File multiple times!");
    exit(1);
  }

  sqlite3_finalize(stmt);
  return id;
}

void FileDB::update_contents(int fileId, int oldContentsId, char md5[32], time_t mtime) {
  cout << "STUB\tupdate_contents" << endl;
}

int FileDB::get_contents_id(size_t size, char md5[33]) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_getContentsId, -1, &stmt, NULL);
  sqlite3_bind_text(stmt, 1, (const char*) md5, 32, SQLITE_TRANSIENT);
  sqlite3_bind_int(stmt, 2, size);

  int ret = sqlite3_step(stmt);
  if (ret == SQLITE_DONE) return 0;
  if (ret != SQLITE_ROW) {
    perror(sqlite3_errmsg(db));
    exit(1);
  }

  int id = sqlite3_column_int(stmt, 0);

  if (sqlite3_step(stmt) != SQLITE_DONE) {
    perror("Contents multiple times!");
    exit(1);
  }

  sqlite3_finalize(stmt);
  return id;
}

int FileDB::get_contents_refs (int id) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_getContentsRefs, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, id);

  int ret = sqlite3_step(stmt);
  if (ret == SQLITE_DONE) return 0;
  if (ret != SQLITE_ROW) {
    perror(sqlite3_errmsg(db));
    exit(1);
  }

  int refs = sqlite3_column_int(stmt, 0);

  if (sqlite3_step(stmt) != SQLITE_DONE) {
    perror("Contents multiple times!");
    exit(1);
  }

  sqlite3_finalize(stmt);
  return refs;
}

void FileDB::add_contents(size_t size, char md5[33]) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_addContents, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, size);
  sqlite3_bind_text(stmt, 2, md5, 32, SQLITE_TRANSIENT);

  int ret = sqlite3_step(stmt);
  if (ret != SQLITE_DONE) {
    cerr << "Error adding contents (" << size << ", " << md5 << "): " << sqlite3_errmsg(db) << endl;
  }
  sqlite3_finalize(stmt);
}

void FileDB::ref_contents(int id) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_incContentsRefs, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, id);

  int ret = sqlite3_step(stmt);
  if (ret != SQLITE_DONE) {
    cerr << "Error ref-ing contents id " << id << ": " << sqlite3_errmsg(db) << endl;
  }
  sqlite3_finalize(stmt);
}

void FileDB::deref_contents(int id) {
  int refs = get_contents_refs(id);

  sqlite3_stmt* stmt;

  if (refs == 1) {
    sqlite3_prepare(db, SQL_delContents, -1, &stmt, NULL);
    sqlite3_bind_int(stmt, 1, id);

    int ret = sqlite3_step(stmt);
    if (ret != SQLITE_DONE) {
      cerr << "Error deleting just-unrefed contents id " << id << ": " << sqlite3_errmsg(db) << endl;
    }
  } else if (refs > 1) {
    sqlite3_prepare(db, SQL_decContentsRefs, -1, &stmt, NULL);
    sqlite3_bind_int(stmt, 1, id);

    int ret = sqlite3_step(stmt);
    if (ret != SQLITE_DONE) {
      cerr << "Error derefing contents id " << id << ": " << sqlite3_errmsg(db) << endl;
    }
  } else {
    cerr << "Contents id " << id << " had refcount 0 and trying to deref!" << endl;
  }

  sqlite3_finalize(stmt);
}

void FileDB::add_file(int pathId, const char* name, size_t size, char md5[33], time_t mtime) {
  cout << "add_file\t" << name << endl;
  S3Transaction t(db);

  // find MD5 in contents
  int contentsId = get_contents_id(size, md5);
  if (contentsId == 0) {
    add_contents(size, md5);
    contentsId = get_contents_id(size, md5);
  }
  ref_contents(contentsId);

  // add in files table
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, SQL_addFile, -1, &stmt, NULL);
  sqlite3_bind_int(stmt, 1, contentsId);
  sqlite3_bind_int(stmt, 2, pathId);
  sqlite3_bind_text(stmt, 3, name, -1, SQLITE_TRANSIENT);
  sqlite3_bind_int(stmt, 4, mtime);

  int ret = sqlite3_step(stmt);
  if (ret != SQLITE_DONE) {
    cerr << "Error adding file (" << name << ", " << pathId << "): " << sqlite3_errmsg(db) << endl;
    deref_contents(contentsId);
  }
  sqlite3_finalize(stmt);

  t.done();
}
